package abstractComponent;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import concreteComponents.ConEstado;

import channel.Channel;

public abstract class ComponenteConEstado {

	Lock lock = new ReentrantLock();
	private Condition siguiente = lock.newCondition();
	private Condition modificando = lock.newCondition();
	private Condition leyendo = lock.newCondition();
	
	boolean estaModificando = false;
	int cantLecturas;
	private List<Thread> cola = new ArrayList<Thread>();

	static Map<String, Class<? extends ComponenteConEstado>> map = new HashMap<String, Class<? extends ComponenteConEstado>>();

	
	public class EntradaThread extends Thread{
		
		private Channel<Serializable> channel;
		private Set<Channel<Serializable>> channelsS;
		
		public EntradaThread(Channel<Serializable> channel0, Set<Channel<Serializable>> channels0) {
			this.channel = channel0;
			this.channelsS = channels0;
		}
		
		@Override
		public void run() {
			while(true){
				String[] argumento = channel.receive().toString().split(" ");
				seleccionarAccion(channelsS, argumento);
			}
		}
	}
	
	class Lectura extends Thread {
		private int argumento;
		Set<Channel<Serializable>> channels;

		public Lectura(int arg, Set<Channel<Serializable>> channels0) {
			argumento = arg;
			channels = channels0;
		}

		public void run() {

			lock.lock();
			cola.add(Thread.currentThread());
			while (cola.get(0) != Thread.currentThread()) {
				siguiente.signal();
				try {
					siguiente.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			while (estaModificando) {
				try {
					modificando.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			cantLecturas++;
			cola.remove(0);
			siguiente.signal();
			lock.unlock();

			Serializable salida = leerEstado(argumento);
			
			lock.lock();
			for (Channel<Serializable> channel : channels) {
				channel.send(salida);
			}
			
			if (--cantLecturas == 0) {
				leyendo.signal();
			}
			lock.unlock();
		}
	}

	class Modificar extends Thread {
		private int arg1;
		private int arg2;
		Set<Channel<Serializable>> channels;

		public Modificar(int argumento1, int argumento2, Set<Channel<Serializable>> channels0) {
			arg1 = argumento1;
			arg2 = argumento2;
			channels = channels0;
		}

		public void run() {
			lock.lock();
			cola.add(Thread.currentThread());
			while (cola.get(0) != Thread.currentThread()) {
				siguiente.signal();
				try {
					siguiente.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			while (cantLecturas > 0) {
				try {
					leyendo.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			while (estaModificando) {
				try {
					modificando.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			estaModificando = true;
			cola.remove(0);
			siguiente.signal();

			Serializable estado = modificarEstado(arg1, arg2);
			for (Channel<Serializable> channel : channels) {
				channel.send(estado);
			}

			estaModificando = false;
			modificando.signal();
			lock.unlock();

		}
	}
	
	public static void main(String[] args) throws InstantiationException,
														IllegalAccessException {
		Set<Channel<Serializable>> channelsS = new HashSet<Channel<Serializable>>();
		Set<Channel<Serializable>> channelsR = new HashSet<Channel<Serializable>>();
		
		map.put("ConEstado", ConEstado.class);
		ComponenteConEstado instance = map.get(args[0]).newInstance();
		
		String flag = "";
		for (int n = 1; n < args.length; n++) {
		
			if (args[n].equals("-cs")) {
				flag = "cs";
				continue;
			}
		
			if (args[n].equals("-cr")) {
				flag = "cr";
				continue;
			}
		
			if (flag.equals("cs")) {
				int i = Integer.parseInt(args[n]);
				channelsS.add(new Channel<Serializable>(i));
			} else {
				int i = Integer.parseInt(args[n]);
				channelsR.add(new Channel<Serializable>(i));
			}
		}
		
		for (Channel<Serializable> channel : channelsR){
			instance.new EntradaThread(channel, channelsS).start();
		}
	}

	public void seleccionarAccion(Set<Channel<Serializable>> channelsS, String[] argumento) {
		if(argumento[0].equals("Leer")){
			int arg = Integer.parseInt(argumento[1]);
			
			Lectura lectura = new Lectura(arg, channelsS);
			lectura.run();
		}
		
		if(argumento[0].equals("Modificar")){
			int arg1 = Integer.parseInt(argumento[1]);
			int arg2 = Integer.parseInt(argumento[2]);
			
			Modificar modificar = new Modificar(arg1, arg2, channelsS);
			modificar.run();
		}
	}
	
	public abstract Serializable modificarEstado(int arg1, int arg2);

	public abstract Serializable leerEstado(int argumento);
	
	
}